<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="反射2">
<meta name="twitter:description" content="<p>使用reflect.Value来设置值：判断值可寻址的方法，设置值的方法，注意事项，判断值可修改的方法；解码 S 表达式：词法分析器，函数实现，封装解析器；访问结构体字段标签：http服务端解析请求参数并完成结构体字段填充；显示类型的方法；慎用反射的三个原因</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/gopl-reflection-2.html"/>
<meta property="og:title" content="来玩魔王的咚 | 反射2" />
<meta property="og:description" content="<p>使用reflect.Value来设置值：判断值可寻址的方法，设置值的方法，注意事项，判断值可修改的方法；解码 S 表达式：词法分析器，函数实现，封装解析器；访问结构体字段标签：http服务端解析请求参数并完成结构体字段填充；显示类型的方法；慎用反射的三个原因</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>使用reflect.Value来设置值：判断值可寻址的方法，设置值的方法，注意事项，判断值可修改的方法；解码 S 表达式：词法分析器，函数实现，封装解析器；访问结构体字段标签：http服务端解析请求参数并完成结构体字段填充；显示类型的方法；慎用反射的三个原因</p>" />

    <meta name="tags" content="gopl" />
    <meta name="tags" content="反射" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li class="active"><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-08-14 11:20:00+08:00">
                <script>document.write(moment('2020-08-14 11:20:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-08-14 11:20:00+08:00">
                <script>document.write(moment('2020-08-14 11:20:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            反射2
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#shi-yong-reflectvalue-lai-she-zhi-zhi">使用 reflect.Value 来设置值</a><ul>
<li><a href="#ke-xun-zhi-de-zhi-canaddr">可寻址的值（canAddr）</a></li>
<li><a href="#geng-xin-bian-liang-set">更新变量（Set）</a></li>
<li><a href="#zhu-yi-shi-xiang">注意事项</a></li>
<li><a href="#ke-xiu-gai-de-zhi-canset">可修改的值（CanSet）</a></li>
</ul>
</li>
<li><a href="#shi-li-jie-ma-s-biao-da-shi">示例：解码 S 表达式</a><ul>
<li><a href="#ci-fa-fen-xi-qi">词法分析器</a></li>
<li><a href="#han-shu-shi-xian">函数实现</a></li>
<li><a href="#feng-zhuang-jie-xi-qi">封装解析器</a></li>
</ul>
</li>
<li><a href="#fang-wen-jie-gou-ti-zi-duan-biao-qian">访问结构体字段标签</a><ul>
<li><a href="#zai-http-chu-li-han-shu-zhong-shi-yong">在HTTP处理函数中使用</a></li>
<li><a href="#gong-ju-han-shu-unpack-de-shi-xian">工具函数 Unpack 的实现</a></li>
<li><a href="#zhi-xing-xiao-guo">执行效果</a></li>
</ul>
</li>
<li><a href="#xian-shi-lei-xing-de-fang-fa">显示类型的方法</a></li>
<li><a href="#zhu-yi-shi-xiang_1">注意事项</a><ul>
<li><a href="#dai-ma-cui-ruo">代码脆弱</a></li>
<li><a href="#nan-li-jie-nan-wei-hu">难理解、难维护</a></li>
<li><a href="#yun-xing-man">运行慢</a></li>
</ul>
</li>
</ul>
</div>
<p><strong>本篇各章节的主要内容：</strong></p>
<ol>
<li>使用 reflect.Value 来设置值：通过 Elem() 方法获取指针对应的值，然后就可以修改值</li>
<li>示例，解码 S 表达式：之前内容的综合运用</li>
<li>访问结构体成员标签：像JSON反序列化那样，使用反射获取成员标签，并填充结构体的字段</li>
<li>显示类型的方法：通过一个简单的示例，获取任意值的类型，并枚举它的方法，还可以调用这些方法</li>
<li>注意事项：慎用反射，原因有三</li>
</ol>
<h3 id="shi-yong-reflectvalue-lai-she-zhi-zhi"><a class="toclink" href="#shi-yong-reflectvalue-lai-she-zhi-zhi">使用 reflect.Value 来设置值</a></h3>
<p>到目前为止，反射只是用来<strong>解析</strong>变量值。本节的重点是<strong>改变</strong>值。  </p>
<h4 id="ke-xun-zhi-de-zhi-canaddr"><a class="toclink" href="#ke-xun-zhi-de-zhi-canaddr">可寻址的值（canAddr）</a></h4>
<p>reflect.Value 的值，有些是可寻址的，有些是不可寻址的。通过 reflect.ValueOf(x) 返回的 reflect.Value 都是不可寻址的。但是通过指针提领得来的 reflect.Value 是可寻址的。可以通过调用 reflect.ValueOf(&amp;x).Elem() 来获得任意变量 x 可寻址的 reflect.Value 值。<br>
可以通过变量的 CanAddr 方法来询问 reflect.Value 变量是否可寻址：</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">                   </span><span class="c1">// value   type    variable?</span><span class="w"></span>
<span class="nx">a</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w">  </span><span class="c1">// 2       int     no</span><span class="w"></span>
<span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w">  </span><span class="c1">// 2       int     no</span><span class="w"></span>
<span class="nx">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">x</span><span class="p">)</span><span class="w"> </span><span class="c1">// &amp;x      *int    no</span><span class="w"></span>
<span class="nx">d</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">Elem</span><span class="p">()</span><span class="w">            </span><span class="c1">// 2       int     yes (x)</span><span class="w"></span>

<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">CanAddr</span><span class="p">())</span><span class="w"> </span><span class="c1">// &quot;false&quot;</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">CanAddr</span><span class="p">())</span><span class="w"> </span><span class="c1">// &quot;false&quot;</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">CanAddr</span><span class="p">())</span><span class="w"> </span><span class="c1">// &quot;false&quot;</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">CanAddr</span><span class="p">())</span><span class="w"> </span><span class="c1">// &quot;true&quot;</span><span class="w"></span>
</code></pre></div>

<h4 id="geng-xin-bian-liang-set"><a class="toclink" href="#geng-xin-bian-liang-set">更新变量（Set）</a></h4>
<p>从一个可寻址的 reflect.Value() 获取变量需要三步：</p>
<ol>
<li>调用 Addr()，返回一个 Value，其中包含一个指向变量的指针</li>
<li>在这个 Value 上调用 interface()，返回一个包含这个指针的 interface{} 值</li>
<li>如果知道变量的类型，使用类型断言把空接口转换为一个普通指针</li>
</ol>
<p>之后，就可以通过这个指针来更新变量了：</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w"></span>
<span class="nx">d</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">x</span><span class="p">).</span><span class="nx">Elem</span><span class="p">()</span><span class="w">   </span><span class="c1">// d代表变量x</span><span class="w"></span>
<span class="nx">px</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">d</span><span class="p">.</span><span class="nx">Addr</span><span class="p">().</span><span class="nx">Interface</span><span class="p">().(</span><span class="o">*</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="c1">// px := &amp;x</span><span class="w"></span>
<span class="o">*</span><span class="nx">px</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">3</span><span class="w">                           </span><span class="c1">// x = 3</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w">                    </span><span class="c1">// &quot;3&quot;</span><span class="w"></span>
</code></pre></div>

<p>还有一个方法，可以直接通过可寻址的 reflect.Value 来更新变量。<br>
不用通过指针，而是直接调用 reflect.Value.Set 方法：</p>
<div class="highlight"><pre><span></span><code><span class="nx">d</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="mi">4</span><span class="p">))</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w"> </span><span class="c1">// &quot;4&quot;</span><span class="w"></span>
</code></pre></div>

<h4 id="zhu-yi-shi-xiang"><a class="toclink" href="#zhu-yi-shi-xiang">注意事项</a></h4>
<p>如果类型不匹配会导致程序崩溃：</p>
<div class="highlight"><pre><span></span><code><span class="nx">d</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nb">int64</span><span class="p">(</span><span class="mi">5</span><span class="p">)))</span><span class="w"> </span><span class="c1">// panic: int64 不可赋值给 int</span><span class="w"></span>
</code></pre></div>

<p>在一个不可寻址的 reflect.Value 上调用 Set 方法也会使程序崩溃：</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w"></span>
<span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w"></span>
<span class="nx">b</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="c1">// panic: 在不可寻址的值上使用 Set 方法</span><span class="w"></span>
</code></pre></div>

<p>另外还提供了一些为基本类型特化的 Set 变种：SetInt、SetUint、SetString、SetFloat等：</p>
<div class="highlight"><pre><span></span><code><span class="nx">d</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">x</span><span class="p">).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="nx">d</span><span class="p">.</span><span class="nx">SetInt</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w"> </span><span class="c1">// &quot;3&quot;</span><span class="w"></span>
</code></pre></div>

<p>这些方法还有一定的容错性。比如 SetInt 方法，任意有符号整型，甚至是底层类型是有符号整型的命名类型，都可以执行成功。如果值太大了，会无提示地截断它。<br>
但是在指向 interface{} 变量的 reflect.Value 上调用 SetInt 会崩溃（尽管使用 Set 是没有问题的）：</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w"></span>
<span class="nx">rx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">x</span><span class="p">).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="nx">rx</span><span class="p">.</span><span class="nx">SetInt</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w">                     </span><span class="c1">// OK, x = 2</span><span class="w"></span>
<span class="nx">rx</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span><span class="w">       </span><span class="c1">// OK, x = 3</span><span class="w"></span>
<span class="nx">rx</span><span class="p">.</span><span class="nx">SetString</span><span class="p">(</span><span class="s">&quot;hello&quot;</span><span class="p">)</span><span class="w">            </span><span class="c1">// panic: string 不能赋值给 int</span><span class="w"></span>
<span class="nx">rx</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="s">&quot;hello&quot;</span><span class="p">))</span><span class="w"> </span><span class="c1">// panic: string 不能赋值给 int</span><span class="w"></span>

<span class="kd">var</span><span class="w"> </span><span class="nx">y</span><span class="w"> </span><span class="kd">interface</span><span class="p">{}</span><span class="w"></span>
<span class="nx">ry</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">y</span><span class="p">).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="nx">ry</span><span class="p">.</span><span class="nx">SetInt</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w">                     </span><span class="c1">// panic: 在指向空接口的 Value 上调用 SetInt</span><span class="w"></span>
<span class="nx">ry</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span><span class="w">       </span><span class="c1">// OK, y = int(3)</span><span class="w"></span>
<span class="nx">ry</span><span class="p">.</span><span class="nx">SetString</span><span class="p">(</span><span class="s">&quot;hello&quot;</span><span class="p">)</span><span class="w">            </span><span class="c1">// panic: 在指向空接口的 Value 上调用 SetString</span><span class="w"></span>
<span class="nx">ry</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="s">&quot;hello&quot;</span><span class="p">))</span><span class="w"> </span><span class="c1">// OK, y = &quot;hello&quot;</span><span class="w"></span>
</code></pre></div>

<h4 id="ke-xiu-gai-de-zhi-canset"><a class="toclink" href="#ke-xiu-gai-de-zhi-canset">可修改的值（CanSet）</a></h4>
<p>另外，反射可以越过 Go 言语的导出规则，读取到未导出的成员。<br>
但是利用反射不能修改未导出的成员：</p>
<div class="highlight"><pre><span></span><code><span class="nx">stdout</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"> </span><span class="c1">// *os.Stdout, 一个 os.File 变量</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">stdout</span><span class="p">.</span><span class="nx">Type</span><span class="p">())</span><span class="w">                  </span><span class="c1">// &quot;os.File&quot;</span><span class="w"></span>
<span class="nx">fd</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">stdout</span><span class="p">.</span><span class="nx">FieldByName</span><span class="p">(</span><span class="s">&quot;fd&quot;</span><span class="p">)</span><span class="w"></span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">fd</span><span class="p">.</span><span class="nx">Int</span><span class="p">())</span><span class="w"> </span><span class="c1">// &quot;1&quot; ，获取到了未导出的成员的值</span><span class="w"></span>
<span class="nx">fd</span><span class="p">.</span><span class="nx">SetInt</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w">          </span><span class="c1">// panic: unexported field ，尝试修改则会崩溃</span><span class="w"></span>
</code></pre></div>

<p>一个可寻址的 reflect.Value 会记录它是否是通过遍历一个未导出的字段来获得的，如果是这样则不允许修改。  </p>
<p>所以在更新变量前用 CanAddr 来检查不能保证正确。<br>
CanSet 方法才能正确地报告一个 reflect.Value 是否可寻址且可更改：</p>
<div class="highlight"><pre><span></span><code><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">fd</span><span class="p">.</span><span class="nx">CanAddr</span><span class="p">(),</span><span class="w"> </span><span class="nx">fd</span><span class="p">.</span><span class="nx">CanSet</span><span class="p">())</span><span class="w"> </span><span class="c1">// &quot;true false&quot;</span><span class="w"></span>
</code></pre></div>

<h3 id="shi-li-jie-ma-s-biao-da-shi"><a class="toclink" href="#shi-li-jie-ma-s-biao-da-shi">示例：解码 S 表达式</a></h3>
<p>本节要为 S 表达式编码实现一个简单的 Unmarshal 函数（解碼器）。一个健壮的和通用的实现比这里的例子需要更多的代码，这里精简了很多，只支持 S 表达式有限的子集，并且没有优雅地处理错误。代码的目的是阐释反射，而不是语法分析。  </p>
<h4 id="ci-fa-fen-xi-qi"><a class="toclink" href="#ci-fa-fen-xi-qi">词法分析器</a></h4>
<p>词法分析器 lexer 使用 text\/scanner 包提供的扫描器 Scanner 类型来把输入流分解成一系列的标记（token），包括注释、标识符、字符串字面量和数字字面量。扫描器的 Scan 方法将提前扫描并返回下一个标记（类型为 rune）。大部分标记（比如'('）都只包含单个rune，但 text\/scanner 包也可以支持由多个字符组成的记号。调用 Scan 会返回标记的类型，调用 TokenText 则会返回标记的文本。<br>
因为每个解析器可能需要多次使用当前的记号，但是 Scan 会一直向前扫描，所以把扫描器封装到一个 lexer 辅助类型中，其中保存了 Scan 最近返回的标记：</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span><span class="w"> </span><span class="nx">lexer</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">scan</span><span class="w">  </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">Scanner</span><span class="w"></span>
<span class="w">    </span><span class="nx">token</span><span class="w"> </span><span class="kt">rune</span><span class="w"> </span><span class="c1">// 当前标记</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">lex</span><span class="w"> </span><span class="o">*</span><span class="nx">lexer</span><span class="p">)</span><span class="w"> </span><span class="nx">next</span><span class="p">()</span><span class="w">        </span><span class="p">{</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">token</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">scan</span><span class="p">.</span><span class="nx">Scan</span><span class="p">()</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">lex</span><span class="w"> </span><span class="o">*</span><span class="nx">lexer</span><span class="p">)</span><span class="w"> </span><span class="nx">text</span><span class="p">()</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">scan</span><span class="p">.</span><span class="nx">TokenText</span><span class="p">()</span><span class="w"> </span><span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">lex</span><span class="w"> </span><span class="o">*</span><span class="nx">lexer</span><span class="p">)</span><span class="w"> </span><span class="nx">consume</span><span class="p">(</span><span class="nx">want</span><span class="w"> </span><span class="kt">rune</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">token</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">want</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="c1">// 注意: 错误处理不是这篇的重点，简单粗暴的处理了</span><span class="w"></span>
<span class="w">        </span><span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Sprintf</span><span class="p">(</span><span class="s">&quot;got %q, want %q&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">(),</span><span class="w"> </span><span class="nx">want</span><span class="p">))</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<h4 id="han-shu-shi-xian"><a class="toclink" href="#han-shu-shi-xian">函数实现</a></h4>
<p>分析器有两个主要的函数：</p>
<ul>
<li><code>read(lex *lexer, v reflect.Value)</code></li>
<li><code>readList(lex *lexer, v reflect.Value)</code></li>
</ul>
<h5 id="read-han-shu"><a class="toclink" href="#read-han-shu">read 函数</a></h5>
<p>一个是read，它读取从当前标记开始的 S 表达式，并更新由可寻址的 reflect.Value 类型的变量 v 指向的变量：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="w"> </span><span class="o">*</span><span class="nx">lexer</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Value</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">switch</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">token</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">Ident</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="c1">// 仅有的有标识符是 “nil” 和结构体的字段名</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&quot;nil&quot;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">v</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Zero</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">()))</span><span class="w"></span>
<span class="w">            </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">String</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nx">s</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nx">Unquote</span><span class="p">(</span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">())</span><span class="w"> </span><span class="c1">// 注意：错误被忽略</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="p">.</span><span class="nx">SetString</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">Int</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nx">Atoi</span><span class="p">(</span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">())</span><span class="w"> </span><span class="c1">// 注意：错误被忽略</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="p">.</span><span class="nx">SetInt</span><span class="p">(</span><span class="nb">int64</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span><span class="w"></span>
<span class="w">        </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="sc">&#39;(&#39;</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"></span>
<span class="w">        </span><span class="nx">readList</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"> </span><span class="c1">// consume &#39;)&#39;</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Sprintf</span><span class="p">(</span><span class="s">&quot;unexpected token %q&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">()))</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>S 表达式为两个不同的目的使用标识符：结构体的字段名和指针的 nil 值。read 函数只处理后一种情况。当它遇到 scanner.Ident 的值为 “nil” 时，通过 reflect.Zero 函数把 v 设置为其类型的零值。对于其他标识符，则应该产生一个错误（这里则是采用简单粗暴的方法，直接忽略了）。  </p>
<h5 id="readlist-han-shu"><a class="toclink" href="#readlist-han-shu">readList 函数</a></h5>
<p>还有一个是 readList 函数。一个 '(' 标记代表一个列表的开始，readList 函数可把列表解码为多种类型：map、结构体、切片或者数组，具体类型根据传入待填充变量的类型决定。对于每种类型都会循环解析内容直到遇到匹配的右括号 ')'，这个是用 endList 函数来检测的。<br>
比较有趣的地方是递归。最简单的例子是处理数组，在遇到 ')' 之前，使用 Index 方法来获得数组的一个元素，再递归调用 read 来填充数据。切片的流程与数组类似，不同之处是先创建每一个元素变量，再填充，最后追加到切片中。<br>
结构体和map在循环的每一轮中都必须解析一个关于(key value)的子列表。对于结构体，key 是用来定位字段的符号。与数组类似，通过 FieldByName 函数来获得结构体对应字段的变量，再递归调用 read 来填充。对于 map，key 可以是任何类型。与切片类似，先创建新变量，再递归地填充，最后再把新的键值对添加到 map中。<br>
具体代码如下：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">readList</span><span class="p">(</span><span class="nx">lex</span><span class="w"> </span><span class="o">*</span><span class="nx">lexer</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Value</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">switch</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Kind</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Array</span><span class="p">:</span><span class="w"> </span><span class="c1">// (item ...)</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="p">!</span><span class="nx">endList</span><span class="p">(</span><span class="nx">lex</span><span class="p">);</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Index</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Slice</span><span class="p">:</span><span class="w"> </span><span class="c1">// (item ...)</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">!</span><span class="nx">endList</span><span class="p">(</span><span class="nx">lex</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">item</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">().</span><span class="nx">Elem</span><span class="p">()).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">item</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="nx">v</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Append</span><span class="p">(</span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">item</span><span class="p">))</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Struct</span><span class="p">:</span><span class="w"> </span><span class="c1">// ((name value) ...)</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">!</span><span class="nx">endList</span><span class="p">(</span><span class="nx">lex</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">lex</span><span class="p">.</span><span class="nx">consume</span><span class="p">(</span><span class="sc">&#39;(&#39;</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">token</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">Ident</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Sprintf</span><span class="p">(</span><span class="s">&quot;got token %q, want field name&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">()))</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">            </span><span class="nx">name</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">text</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">FieldByName</span><span class="p">(</span><span class="nx">name</span><span class="p">))</span><span class="w"></span>
<span class="w">            </span><span class="nx">lex</span><span class="p">.</span><span class="nx">consume</span><span class="p">(</span><span class="sc">&#39;)&#39;</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Map</span><span class="p">:</span><span class="w"> </span><span class="c1">// ((key value) ...)</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">MakeMap</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">()))</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">!</span><span class="nx">endList</span><span class="p">(</span><span class="nx">lex</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">lex</span><span class="p">.</span><span class="nx">consume</span><span class="p">(</span><span class="sc">&#39;(&#39;</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="nx">key</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">().</span><span class="nx">Key</span><span class="p">()).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">().</span><span class="nx">Elem</span><span class="p">()).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="nx">v</span><span class="p">.</span><span class="nx">SetMapIndex</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="p">)</span><span class="w"></span>
<span class="w">            </span><span class="nx">lex</span><span class="p">.</span><span class="nx">consume</span><span class="p">(</span><span class="sc">&#39;)&#39;</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="k">default</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nb">panic</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Sprintf</span><span class="p">(</span><span class="s">&quot;cannot decode list into %v&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">()))</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">endList</span><span class="p">(</span><span class="nx">lex</span><span class="w"> </span><span class="o">*</span><span class="nx">lexer</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">switch</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">token</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">EOF</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nb">panic</span><span class="p">(</span><span class="s">&quot;end of file&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="sc">&#39;)&#39;</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<h4 id="feng-zhuang-jie-xi-qi"><a class="toclink" href="#feng-zhuang-jie-xi-qi">封装解析器</a></h4>
<p>最后，把解析器封装成如下所示的一个导出的函数 Unmarshal，隐藏了实现中多个不完美的地方，比如解析过程中遇到错误会崩溃，因此使用了一个延迟调用来从崩溃中恢复，并且返回错误消息：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// Unmarshal 解析 S 表达式数据并且填充到非 nil 指针 out 指向的变量</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">,</span><span class="w"> </span><span class="nx">out</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">(</span><span class="nx">err</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">lex</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">lexer</span><span class="p">{</span><span class="nx">scan</span><span class="p">:</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">Scanner</span><span class="p">{</span><span class="nx">Mode</span><span class="p">:</span><span class="w"> </span><span class="nx">scanner</span><span class="p">.</span><span class="nx">GoTokens</span><span class="p">}}</span><span class="w"></span>
<span class="w">    </span><span class="nx">lex</span><span class="p">.</span><span class="nx">scan</span><span class="p">.</span><span class="nx">Init</span><span class="p">(</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">NewReader</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span><span class="w"></span>
<span class="w">    </span><span class="nx">lex</span><span class="p">.</span><span class="nx">next</span><span class="p">()</span><span class="w"> </span><span class="c1">// 获取第一个标记</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="c1">// 注意: 错误处理不是这篇的重点，简单粗暴的处理了</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">recover</span><span class="p">();</span><span class="w"> </span><span class="nx">x</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;error at %s: %v&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">lex</span><span class="p">.</span><span class="nx">scan</span><span class="p">.</span><span class="nx">Position</span><span class="p">,</span><span class="w"> </span><span class="nx">x</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}()</span><span class="w"></span>
<span class="w">    </span><span class="nx">read</span><span class="p">(</span><span class="nx">lex</span><span class="p">,</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nx">out</span><span class="p">).</span><span class="nx">Elem</span><span class="p">())</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>一个具备用于生产环境的质量的实现对任何的输入都不应当崩溃，而且应当对每次错误详细报告信息，可能的话，应当包含行号或者偏移量。通过这个示例有助于了解 encoding/json 这类包的底层机制，以及如何使用反射来填充数据结构。  </p>
<h3 id="fang-wen-jie-gou-ti-zi-duan-biao-qian"><a class="toclink" href="#fang-wen-jie-gou-ti-zi-duan-biao-qian">访问结构体字段标签</a></h3>
<p><em>这里的“成员”和“字段”两个词有点混用，但都是同一个意思。</em><br>
可以使用结构体<strong>成员标签</strong>（field tag）在进行JSON反序列化的时候对应JSON中字段的名字。json 成员标签让我们可以选择其他的字段名以及忽略输出的空字段。这小节将通过反射机制获取结构体字段的标签，然后填充字段的值，就和JSON反序列化一样，目标和结果是一样的，只是获取的数据源不同。<br>
有一个 Web 服务应用的场景，在 Web 服务器中，绝大部分 HTTP 处理函数的第一件事就是提取请求参数到局部变量中。这里将定义一个工具函数 params.Unpack，使用结构体成员标签直接将参数填充到结构体对应的字段中。因为 URL 的长度有限，所以参数的名称一般比较短，含义也比较模糊。这需要通过成员标签将结构体的字段和参数名称对应上。  </p>
<h4 id="zai-http-chu-li-han-shu-zhong-shi-yong"><a class="toclink" href="#zai-http-chu-li-han-shu-zhong-shi-yong">在HTTP处理函数中使用</a></h4>
<p>首先，展示这个工具函数的用法。就是假设已经实现了这个 params.Unpack 函数，下面的 search 函数就是一个 HTTP 处理函数，它定义了一个结构体变量 data，data 也定义了成员标签来对应请求参数的名字。Unpack 函数从请求中提取数据来填充这个结构体，这样不仅可以更方便的访问，还避免了手动转换类型。  </p>
<p>search 函数的代码如下：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;net/http&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="s">&quot;gopl/ch12/params&quot;</span><span class="w"></span>

<span class="c1">// search 用于处理 /search URL endpoint.</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">search</span><span class="p">(</span><span class="nx">resp</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">req</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">Labels</span><span class="w">     </span><span class="p">[]</span><span class="kt">string</span><span class="w"> </span><span class="s">`http:&quot;l&quot;`</span><span class="w"></span>
<span class="w">        </span><span class="nx">MaxResults</span><span class="w"> </span><span class="kt">int</span><span class="w">      </span><span class="s">`http:&quot;max&quot;`</span><span class="w"></span>
<span class="w">        </span><span class="nx">Exact</span><span class="w">      </span><span class="kt">bool</span><span class="w">     </span><span class="s">`http:&quot;x&quot;`</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nx">data</span><span class="p">.</span><span class="nx">MaxResults</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="c1">// 设置默认值</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">params</span><span class="p">.</span><span class="nx">Unpack</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">data</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">http</span><span class="p">.</span><span class="nx">Error</span><span class="p">(</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">.</span><span class="nx">Error</span><span class="p">(),</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">)</span><span class="w"> </span><span class="c1">// 400</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="c1">// ...其他处理代码...</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Fprintf</span><span class="p">(</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;Search: %+v\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="c1">// 这里还缺少一个 main 函数，最后会补上</span><span class="w"></span>
</code></pre></div>

<h4 id="gong-ju-han-shu-unpack-de-shi-xian"><a class="toclink" href="#gong-ju-han-shu-unpack-de-shi-xian">工具函数 Unpack 的实现</a></h4>
<p>下面的 Unpack 函数做了三件事情：<br>
一、调用 req.ParseForm() 来解析请求。在这之后，req.Form 就有了所有的请求参数，这个方法对 HTTP 的 GET 和 POST 请求都适用。<br>
二、Unpack 函数构造了一个从每个<strong>有效</strong>字段名到对应字段变量的映射。在字段有标签时，有效字段名与实际字段名可以不同。reflect.Type 的 Field 方法会返回一个 reflect.StructField 类型，这个类型提供了每个字段的名称、类型以及一个可选的标签。它的 Tag 字段类型为 reflect.StructTag，底层类型为字符串，提供了一个 Get 方法用于解析和提取对于一个特定 key 的子串，比如上面示例中结构体字段后面的 <code>http:"max"</code> 这种形式的字段标签。<br>
三、Unpack 遍历 HTTP 参数中的所有 key\/value 对，并且更新对应的结构体字段。同一个参数可以出现多次。如果对应的字段是切片，则参数所有的值都会追加到切片里。否则，这个字段会被多次覆盖，只有最后一次的值才有效。  </p>
<p>Unpack 函数的代码如下：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// Unpack 从 HTTP 请求 req 的参数中提取数据填充到 ptr 指向的结构体的各个字段</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">Unpack</span><span class="p">(</span><span class="nx">req</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">,</span><span class="w"> </span><span class="nx">ptr</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">ParseForm</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="c1">// 创建字段映射表，key 为有效名称</span><span class="w"></span>
<span class="w">    </span><span class="nx">fields</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Value</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nx">ptr</span><span class="p">).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"> </span><span class="c1">// reflect.ValueOf(&amp;x).Elem() 获得任意变量 x 可寻址的值，用于设置值。</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">NumField</span><span class="p">();</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">fieldInfo</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">().</span><span class="nx">Field</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span><span class="w"> </span><span class="c1">// a reflect.StructField，提供了每个字段的名称、类型以及一个可选的标签</span><span class="w"></span>
<span class="w">        </span><span class="nx">tag</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fieldInfo</span><span class="p">.</span><span class="nx">Tag</span><span class="w">           </span><span class="c1">// a reflect.Structtag，底层类型为字符串，提供了一个 Get 方法，下一行就用到了</span><span class="w"></span>
<span class="w">        </span><span class="nx">name</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tag</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="s">&quot;http&quot;</span><span class="p">)</span><span class="w">        </span><span class="c1">// Get 方法用于解析和提取对于一个特定 key 的子串</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="nx">name</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nx">ToLower</span><span class="p">(</span><span class="nx">fieldInfo</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="nx">fields</span><span class="p">[</span><span class="nx">name</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Field</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="c1">// 对请求中的每个参数更新结构体中对应的字段</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">values</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nx">Form</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">f</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fields</span><span class="p">[</span><span class="nx">name</span><span class="p">]</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">f</span><span class="p">.</span><span class="nx">IsValid</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">continue</span><span class="w"> </span><span class="c1">// 忽略不能识别的 HTTP 参数</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">values</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">f</span><span class="p">.</span><span class="nx">Kind</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Slice</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="nx">elem</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span><span class="nx">f</span><span class="p">.</span><span class="nx">Type</span><span class="p">().</span><span class="nx">Elem</span><span class="p">()).</span><span class="nx">Elem</span><span class="p">()</span><span class="w"></span>
<span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">populate</span><span class="p">(</span><span class="nx">elem</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                    </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;%s: %v&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w">                </span><span class="p">}</span><span class="w"></span>
<span class="w">                </span><span class="nx">f</span><span class="p">.</span><span class="nx">Set</span><span class="p">(</span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Append</span><span class="p">(</span><span class="nx">f</span><span class="p">,</span><span class="w"> </span><span class="nx">elem</span><span class="p">))</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">populate</span><span class="p">(</span><span class="nx">f</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                    </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;%s: %v&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w">                </span><span class="p">}</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>这里还调用了一个 populate 函数，负责从单个 HTTP 请求参数值填充单个字段 v （或者切片字段中的单个元素）。目前，它仅支持字符串、有符号整数和布尔值。要支持其他类型可以再添加。  </p>
<p>populate 函数的代码如下：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">populate</span><span class="p">(</span><span class="nx">v</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Value</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">switch</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Kind</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">String</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="p">.</span><span class="nx">SetString</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w"></span>

<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Int</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nx">ParseInt</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="p">.</span><span class="nx">SetInt</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span><span class="w"></span>

<span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">Bool</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strconv</span><span class="p">.</span><span class="nx">ParseBool</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="nx">v</span><span class="p">.</span><span class="nx">SetBool</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span><span class="w"></span>

<span class="w">    </span><span class="k">default</span><span class="p">:</span><span class="w"></span>
<span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Errorf</span><span class="p">(</span><span class="s">&quot;unsupported kind %s&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">())</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<h4 id="zhi-xing-xiao-guo"><a class="toclink" href="#zhi-xing-xiao-guo">执行效果</a></h4>
<p>接着把 search 处理程序添加到一个 Web 服务器中，直接在 search 所在的 main 包的命令源码文件中添加下面的 main 函数：</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search&quot;</span><span class="p">)</span><span class="w">                                 </span><span class="c1">// Search: {Labels:[] MaxResults:10 Exact:false}</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search?l=golang&amp;l=gopl&quot;</span><span class="p">)</span><span class="w">                 </span><span class="c1">// Search: {Labels:[golang gopl] MaxResults:10 Exact:false}</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search?l=gopl&amp;x=1&quot;</span><span class="p">)</span><span class="w">                      </span><span class="c1">// Search: {Labels:[gopl] MaxResults:10 Exact:true}</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search?x=true&amp;max=100&amp;max=200&amp;l=golang&quot;</span><span class="p">)</span><span class="w"> </span><span class="c1">// Search: {Labels:[golang] MaxResults:200 Exact:true}</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search?q=hello&quot;</span><span class="p">)</span><span class="w">                         </span><span class="c1">// Search: {Labels:[] MaxResults:10 Exact:false}  # 不存在的参数会忽略</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search?x=123&quot;</span><span class="p">)</span><span class="w">                           </span><span class="c1">// x: strconv.ParseBool: parsing &quot;123&quot;: invalid syntax  # x 提供的参数解析错误</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;http://localhost:8000/search?max=lots&quot;</span><span class="p">)</span><span class="w">                        </span><span class="c1">// max: strconv.ParseInt: parsing &quot;lots&quot;: invalid syntax  # max 提供的参数解析错误</span><span class="w"></span>
<span class="w">    </span><span class="nx">http</span><span class="p">.</span><span class="nx">HandleFunc</span><span class="p">(</span><span class="s">&quot;/search&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">search</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">ListenAndServe</span><span class="p">(</span><span class="s">&quot;:8000&quot;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">))</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>这里提供了几个示例以及输出的结果，直接使用浏览器，输入URL就能返回对应的结果。  </p>
<h3 id="xian-shi-lei-xing-de-fang-fa"><a class="toclink" href="#xian-shi-lei-xing-de-fang-fa">显示类型的方法</a></h3>
<p>通过反射的 reflect.Type 来获取一个任意值的类型并枚举它的方法。<br>
下面的例子是把类型和方法都打印出来：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">methods</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;reflect&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;strings&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="c1">// Print 输出值 x 的所有方法</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">Print</span><span class="p">(</span><span class="nx">x</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">reflect</span><span class="p">.</span><span class="nx">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="nx">t</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Type</span><span class="p">()</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;type %s\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">t</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">NumMethod</span><span class="p">();</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">methType</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">v</span><span class="p">.</span><span class="nx">Method</span><span class="p">(</span><span class="nx">i</span><span class="p">).</span><span class="nx">Type</span><span class="p">()</span><span class="w"></span>
<span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;func (%s) %s%s\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">t</span><span class="p">.</span><span class="nx">Method</span><span class="p">(</span><span class="nx">i</span><span class="p">).</span><span class="nx">Name</span><span class="p">,</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nx">TrimPrefix</span><span class="p">(</span><span class="nx">methType</span><span class="p">.</span><span class="nx">String</span><span class="p">(),</span><span class="w"> </span><span class="s">&quot;func&quot;</span><span class="p">))</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>reflect.Type 和 reflect.Value 都有一个叫作 Method 的方法：</p>
<ul>
<li>每个 t.Method(i) 都会返回一个 reflect.Method 类型的实例，这个结构类型描述了这个方法的名称和类型。</li>
<li>每个 v.Method(i) 都会返回一个 reflect.Value，代表一个方法值，即一个已经绑定接收者的方法。</li>
</ul>
<p>下面是两个示例测试，展示以及验证上面的函数：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">methods_test</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;strings&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;time&quot;</span><span class="w"></span>

<span class="w">    </span><span class="s">&quot;gopl/ch12/methods&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">ExamplePrintDuration</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">methods</span><span class="p">.</span><span class="nx">Print</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Hour</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="c1">// Output:</span><span class="w"></span>
<span class="w">    </span><span class="c1">// type time.Duration</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) Hours() float64</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) Minutes() float64</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) Nanoseconds() int64</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) Round(time.Duration) time.Duration</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) Seconds() float64</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) String() string</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (time.Duration) Truncate(time.Duration) time.Duration</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">ExamplePrintReplacer</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">methods</span><span class="p">.</span><span class="nx">Print</span><span class="p">(</span><span class="nb">new</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nx">Replacer</span><span class="p">))</span><span class="w"></span>
<span class="w">    </span><span class="c1">// Output:</span><span class="w"></span>
<span class="w">    </span><span class="c1">// type *strings.Replacer</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (*strings.Replacer) Replace(string) string</span><span class="w"></span>
<span class="w">    </span><span class="c1">// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>另外还有一个 reflect.Value.Call 方法，可以调用 Func 类型的 Value，这里没有演示。  </p>
<h3 id="zhu-yi-shi-xiang_1"><a class="toclink" href="#zhu-yi-shi-xiang_1">注意事项</a></h3>
<p>还有很多反射API，这里的示例展示了反射能做哪些事情。<br>
反射是一个功能和表达能力都很强大的工具，但是要慎用，主要有三个原因。  </p>
<h4 id="dai-ma-cui-ruo"><a class="toclink" href="#dai-ma-cui-ruo">代码脆弱</a></h4>
<p>基于反射的代码是很脆弱的。一般编译器在编译时就能报告错误，但是反射错误则要等到执行时才会以崩溃的方式来报告。这可能是等待程序运行很久以后才会发生。<br>
比如，尝试读取一个字符串然后填充一个 Int 类型的变量，那么调用 reflect.Value.SetString 就会崩溃。很多使用反射的程序都会有类似的风险。所以对每一个 reflect.Value 都需要仔细检查它的类型、是否可寻址、是否可设置。<br>
要回避这种缺陷的最好的办法就是确保反射的使用完整的封装在包里，并且如果可能，在包的 API 中避免使用 reflect.Value，尽量使用特定的类型来确保输入是合法的值。如果做不到，那就需要在每个危险的操作前都做额外的动态检查。比如标准库的 fmt.Printf 可以作为一个示例，当遇到操作数类型不合适时，它不会崩溃，而是输出一条描述性的错误消息。这尽管仍然会有 bug，但定位起来就简单多了：</p>
<div class="highlight"><pre><span></span><code><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;%d %s\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;hello&quot;</span><span class="p">,</span><span class="w"> </span><span class="mi">123</span><span class="p">)</span><span class="w"> </span><span class="c1">// %!d(string=hello) %!s(int=123)</span><span class="w"></span>
</code></pre></div>

<p>反射还降低了自动重构和分析工具的安全性与准确度，因为它们无法检测到类型的信息。  </p>
<h4 id="nan-li-jie-nan-wei-hu"><a class="toclink" href="#nan-li-jie-nan-wei-hu">难理解、难维护</a></h4>
<p>类型也算是某种形式的文档，而反射的相关操作则无法做静态类型检查，所以大量使用反射的代码是很难理解的。对应接收 interface{} 或者reflect.Value 的函数，一定要写清楚期望的参数类型和其他限制条件。  </p>
<h4 id="yun-xing-man"><a class="toclink" href="#yun-xing-man">运行慢</a></h4>
<p>基于反射的函数会比为特定类型优化的函数慢一到两个数量级。在一个典型的程序中，大部分函数与整体性能无关，所以为了让程序更清晰可以使用反射。比如测试就很适合使用反射，因为大部分测试都使用小数据集。但对性能关键路径上的函数，最好避免使用反射。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>